#include "..\include\pcvcore.h"
#include <CL\cl.h>
#include <stdlib.h>
#include <memory.h>

cl_context					g_device_context = NULL;
cl_command_queue			g_device_command_queue = NULL;
cl_kernel					g_kernel_list[pcvClsMAX][pcvFuncMax];

PCVAPI(PcvStatus, pcvInit, (
	))
{
	cl_int					errcode = CL_SUCCESS;

	// Get an OpenCL platform
	cl_platform_id			platform_id;
	errcode = clGetPlatformIDs(1, &platform_id, NULL);
	if (errcode != CL_SUCCESS)
	{
		return pcvStsInitHardwareErr;
	}

	// Get the devices
	cl_device_id			device_id;
	errcode = clGetDeviceIDs(platform_id, CL_DEVICE_TYPE_GPU, 1, &device_id, NULL);
	if (errcode != CL_SUCCESS)
	{
		return pcvStsInitHardwareErr;
	}

	// Get platform infomation
	// CL_PLATFORM_PROFILE
	// CL_PLATFORM_VERSION
	// CL_PLATFORM_NAME
	// CL_PLATFORM_VENDOR
	// CL_PLATFORM_EXTENSIONS
	cl_platform_info		platform_info = CL_PLATFORM_EXTENSIONS;
	char					platform_info_data[1024];
	size_t					platform_info_data_len;
	errcode = clGetPlatformInfo(platform_id, platform_info, 1024, platform_info_data, &platform_info_data_len);
	if (errcode != CL_SUCCESS)
	{
		return pcvStsInitHardwareErr;
	}

	// Get device information
	// CL_DEVICE_TYPE
	// CL_DEVICE_VENDOR_ID
	// CL_DEVICE_MAX_COMPUTE_UNITS
	// CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS
	// CL_DEVICE_MAX_WORK_GROUP_SIZE
	// CL_DEVICE_MAX_WORK_ITEM_SIZES
	// CL_DEVICE_PREFERRED_VECTOR_WIDTH_CHAR
	// CL_DEVICE_PREFERRED_VECTOR_WIDTH_SHORT
	// CL_DEVICE_PREFERRED_VECTOR_WIDTH_INT
	// CL_DEVICE_PREFERRED_VECTOR_WIDTH_LONG
	// CL_DEVICE_PREFERRED_VECTOR_WIDTH_FLOAT
	// CL_DEVICE_PREFERRED_VECTOR_WIDTH_DOUBLE
	// CL_DEVICE_MAX_CLOCK_FREQUENCY
	// CL_DEVICE_ADDRESS_BITS
	// CL_DEVICE_MAX_READ_IMAGE_ARGS
	// CL_DEVICE_MAX_WRITE_IMAGE_ARGS
	// CL_DEVICE_MAX_MEM_ALLOC_SIZE
	// CL_DEVICE_IMAGE2D_MAX_WIDTH
	// CL_DEVICE_IMAGE2D_MAX_HEIGHT
	// CL_DEVICE_IMAGE3D_MAX_WIDTH
	// CL_DEVICE_IMAGE3D_MAX_HEIGHT
	// CL_DEVICE_IMAGE3D_MAX_DEPTH
	// CL_DEVICE_IMAGE_SUPPORT
	// CL_DEVICE_MAX_PARAMETER_SIZE
	// CL_DEVICE_MAX_SAMPLERS
	// CL_DEVICE_MEM_BASE_ADDR_ALIGN
	// CL_DEVICE_MIN_DATA_TYPE_ALIGN_SIZE
	// CL_DEVICE_SINGLE_FP_CONFIG
	// CL_DEVICE_GLOBAL_MEM_CACHE_TYPE
	// CL_DEVICE_GLOBAL_MEM_CACHELINE_SIZE
	// CL_DEVICE_GLOBAL_MEM_CACHE_SIZE
	// CL_DEVICE_GLOBAL_MEM_SIZE
	// CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE
	// CL_DEVICE_MAX_CONSTANT_ARGS
	// CL_DEVICE_LOCAL_MEM_TYPE
	// CL_DEVICE_LOCAL_MEM_SIZE
	// CL_DEVICE_ERROR_CORRECTION_SUPPORT
	// CL_DEVICE_PROFILING_TIMER_RESOLUTION
	// CL_DEVICE_ENDIAN_LITTLE
	// CL_DEVICE_AVAILABLE
	// CL_DEVICE_COMPILER_AVAILABLE
	// CL_DEVICE_EXECUTION_CAPABILITIES
	// CL_DEVICE_QUEUE_PROPERTIES
	// CL_DEVICE_NAME
	// CL_DEVICE_VENDOR
	// CL_DRIVER_VERSION
	// CL_DEVICE_PROFILE
	// CL_DEVICE_VERSION
	// CL_DEVICE_EXTENSIONS
	// CL_DEVICE_PLATFORM
	cl_device_info			device_info = CL_DEVICE_VERSION;
	char					device_info_data[1024];
	size_t					device_info_data_len;
	errcode = clGetDeviceInfo(device_id, device_info, 1024, device_info_data, &device_info_data_len);
	if (errcode != CL_SUCCESS)
	{
		return pcvStsInitHardwareErr;
	}
	
	// Create the context
	g_device_context = clCreateContext(0, 1, &device_id, NULL, NULL, &errcode);
	if (errcode != CL_SUCCESS)
	{
		return pcvStsInitHardwareErr;
	}

	// Create a command-queue
	g_device_command_queue = clCreateCommandQueue(g_device_context, device_id, 0, &errcode);
	if (errcode != CL_SUCCESS)
	{
		clReleaseContext(g_device_context);
		return pcvStsInitHardwareErr;
	}

	memset(g_kernel_list, 0, sizeof(cl_kernel) * pcvClsMAX * pcvFuncMax);

	return pcvStsNoErr;
}

PCVAPI(PcvStatus, pcvClean, (
	))
{
	cl_int					errcode = CL_SUCCESS;

	for (int i = 0; i < pcvClsMAX; i++)
	{
		for (int j = 0; j < pcvFuncMax; j++)
		{
			if (g_kernel_list[i][j] != NULL)
			{
				clReleaseKernel(g_kernel_list[i][j]);
			}
		}
	}

	if (g_device_command_queue != NULL)
	{
		errcode = clReleaseCommandQueue(g_device_command_queue);
		if (errcode != CL_SUCCESS)
		{
			return pcvStsErr;
		}
	}
	else
	{
		return pcvStsNoInitErr;
	}

	if (g_device_context != NULL)
	{
		errcode = clReleaseContext(g_device_context);
		if (errcode != CL_SUCCESS)
		{
			return pcvStsErr;
		}
	}
	else
	{
		return pcvStsNoInitErr;
	}

	return pcvStsNoErr;
}

PCVAPI(void*, pcvDeviceMalloc, (
	int						host_length
	))
{
	cl_int					errcode = CL_SUCCESS;

	if (g_device_context == NULL)
	{
		return NULL;
	}

	// CL_MEM_READ_WRITE
	// CL_MEM_ALLOC_HOST_PTR
	cl_mem					device_mem = NULL;
	device_mem = clCreateBuffer(g_device_context, CL_MEM_READ_WRITE, host_length, NULL, &errcode);

	return (void*)device_mem;
}

PCVAPI(void*, pcvDeviceMalloc_8u_C1, (
	const PcvSize			host_size
	))
{
	cl_int					errcode = CL_SUCCESS;

	if (g_device_context == NULL)
	{
		return NULL;
	}

	cl_mem					device_mem = NULL;
	cl_image_format			image_format = {CL_A, CL_UNORM_INT8};
	device_mem = clCreateImage2D(g_device_context, CL_MEM_READ_WRITE, &image_format, host_size.width, host_size.height, 0, NULL, &errcode);

	return (void*)device_mem;
}

PCVAPI(void, pcvDeviceFree, (
	void*					device_ptr
	))
{
	clReleaseMemObject((cl_mem)device_ptr);
}

PCVAPI(PcvStatus, pcvCopyHostToDevice, (
	void*					host_src,
	void*					device_dst,
	int						host_length
	))
{
	cl_int					errcode = CL_SUCCESS;

	cl_event				event_ret;
	errcode = clEnqueueWriteBuffer(g_device_command_queue, (cl_mem)device_dst, CL_TRUE, 0, host_length, host_src, 0, NULL, &event_ret);
	if (errcode != CL_SUCCESS)
	{
		return pcvStsErr;
	}

	errcode = clWaitForEvents(1, &event_ret);
	if (errcode != CL_SUCCESS)
	{
		return pcvStsErr;
	}

	cl_event_info			event_info = CL_EVENT_COMMAND_EXECUTION_STATUS;
	cl_int					event_status;
	errcode = clGetEventInfo(event_ret, event_info, sizeof(cl_int), &event_status, NULL);
	if (errcode != CL_SUCCESS || event_status != CL_COMPLETE)
	{
		return pcvStsErr;
	}

	return pcvStsNoErr;
}

PCVAPI(PcvStatus, pcvCopyHostToDevice_8u_C1, (
	void*					host_src,
	void*					device_dst,
	const PcvSize			host_size
	))
{
	cl_int					errcode = CL_SUCCESS;

	size_t					origin[3] = {0, 0, 0};
	size_t					region[3] = {host_size.width, host_size.height, 1};
	cl_event				event_ret;
	errcode = clEnqueueWriteImage(g_device_command_queue, (cl_mem)device_dst, CL_TRUE, origin, region, 0, 0, host_src, 0, NULL, &event_ret);
	if (errcode != CL_SUCCESS)
	{
		return pcvStsErr;
	}

	errcode = clWaitForEvents(1, &event_ret);
	if (errcode != CL_SUCCESS)
	{
		return pcvStsErr;
	}

	cl_event_info			event_info = CL_EVENT_COMMAND_EXECUTION_STATUS;
	cl_int					event_status;
	errcode = clGetEventInfo(event_ret, event_info, sizeof(cl_int), &event_status, NULL);
	if (errcode != CL_SUCCESS || event_status != CL_COMPLETE)
	{
		return pcvStsErr;
	}

	return pcvStsNoErr;
}

PCVAPI(PcvStatus, pcvCopyHostToDevice_8u_C1R, (
	void*					host_src,
	void*					device_dst,
	const PcvSize			host_size,
	const PcvRect			host_roi
	))
{
	cl_int					errcode = CL_SUCCESS;

	size_t					origin[3] = {host_roi.x, host_roi.y, 0};
	size_t					region[3] = {host_roi.width, host_roi.height, 1};
	cl_event				event_ret;
	errcode = clEnqueueWriteImage(g_device_command_queue, (cl_mem)device_dst, CL_TRUE, origin, region, host_size.width * sizeof(CL_UNORM_INT8), 0, host_src, 0, NULL, &event_ret);
	if (errcode != CL_SUCCESS)
	{
		return pcvStsErr;
	}

	errcode = clWaitForEvents(1, &event_ret);
	if (errcode != CL_SUCCESS)
	{
		return pcvStsErr;
	}

	cl_event_info			event_info = CL_EVENT_COMMAND_EXECUTION_STATUS;
	cl_int					event_status;
	errcode = clGetEventInfo(event_ret, event_info, sizeof(cl_int), &event_status, NULL);
	if (errcode != CL_SUCCESS || event_status != CL_COMPLETE)
	{
		return pcvStsErr;
	}

	return pcvStsNoErr;
}

PCVAPI(PcvStatus, pcvCopyDeviceToHost, (
	void*					device_src,
	void*					host_dst,
	int						host_length
	))
{
	cl_int					errcode = CL_SUCCESS;

//	errcode = clFinish(g_device_command_queue);
	if (errcode != CL_SUCCESS)
	{
		return pcvStsErr;
	}

	cl_event				event_ret;
	errcode = clEnqueueReadBuffer(g_device_command_queue, (cl_mem)device_src, CL_TRUE, 0, host_length, host_dst, 0, NULL, &event_ret);
	if (errcode != CL_SUCCESS)
	{
		return pcvStsErr;
	}

	errcode = clWaitForEvents(1, &event_ret);
	if (errcode != CL_SUCCESS)
	{
		return pcvStsErr;
	}

	cl_event_info			event_info = CL_EVENT_COMMAND_EXECUTION_STATUS;
	cl_int					event_status;
	errcode = clGetEventInfo(event_ret, event_info, sizeof(cl_int), &event_status, NULL);
	if (errcode != CL_SUCCESS || event_status != CL_COMPLETE)
	{
		return pcvStsErr;
	}

	return pcvStsNoErr;
}

PCVAPI(PcvStatus, pcvCopyDeviceToHost_8u_C1, (
	void*					device_src,
	void*					host_dst,
	const PcvSize			host_size
	))
{
	cl_int					errcode = CL_SUCCESS;

	size_t					origin[3] = {0, 0, 0};
	size_t					region[3] = {host_size.width, host_size.height, 1};
	cl_event				event_ret;
	errcode = clEnqueueReadImage(g_device_command_queue, (cl_mem)device_src, CL_TRUE, origin, region, 0, 0, host_dst, 0, NULL, &event_ret);
	if (errcode != CL_SUCCESS)
	{
		return pcvStsErr;
	}

	errcode = clWaitForEvents(1, &event_ret);
	if (errcode != CL_SUCCESS)
	{
		return pcvStsErr;
	}

	cl_event_info			event_info = CL_EVENT_COMMAND_EXECUTION_STATUS;
	cl_int					event_status;
	errcode = clGetEventInfo(event_ret, event_info, sizeof(cl_int), &event_status, NULL);
	if (errcode != CL_SUCCESS || event_status != CL_COMPLETE)
	{
		return pcvStsErr;
	}

	return pcvStsNoErr;
}

PCVAPI(PcvStatus, pcvCopyDeviceToHost_8u_C1R, (
	void*					device_src,
	void*					host_dst,
	const PcvSize			host_size,
	const PcvRect			host_roi
	))
{
	cl_int					errcode = CL_SUCCESS;

	size_t					origin[3] = {host_roi.x, host_roi.y, 0};
	size_t					region[3] = {host_roi.width, host_roi.height, 1};
	cl_event				event_ret;
	errcode = clEnqueueReadImage(g_device_command_queue, (cl_mem)device_src, CL_TRUE, origin, region, host_size.width * sizeof(CL_UNORM_INT8), 0, host_dst, 0, NULL, &event_ret);
	if (errcode != CL_SUCCESS)
	{
		return pcvStsErr;
	}

	errcode = clWaitForEvents(1, &event_ret);
	if (errcode != CL_SUCCESS)
	{
		return pcvStsErr;
	}

	cl_event_info			event_info = CL_EVENT_COMMAND_EXECUTION_STATUS;
	cl_int					event_status;
	errcode = clGetEventInfo(event_ret, event_info, sizeof(cl_int), &event_status, NULL);
	if (errcode != CL_SUCCESS || event_status != CL_COMPLETE)
	{
		return pcvStsErr;
	}

	return pcvStsNoErr;
}

PCVAPI(PcvStatus, pcvCopyDeviceToDevice, (
	void*					device_src,
	void*					device_dst,
	int						host_length
	))
{
	cl_int					errcode = CL_SUCCESS;

	cl_event				event_ret;
	errcode = clEnqueueCopyBuffer(g_device_command_queue, (cl_mem)device_src, (cl_mem)device_dst, 0, 0, host_length, 0, NULL, &event_ret);
	if (errcode != CL_SUCCESS)
	{
		return pcvStsErr;
	}

	errcode = clWaitForEvents(1, &event_ret);
	if (errcode != CL_SUCCESS)
	{
		return pcvStsErr;
	}

	cl_event_info			event_info = CL_EVENT_COMMAND_EXECUTION_STATUS;
	cl_int					event_status;
	errcode = clGetEventInfo(event_ret, event_info, sizeof(cl_int), &event_status, NULL);
	if (errcode != CL_SUCCESS || event_status != CL_COMPLETE)
	{
		return pcvStsErr;
	}

	return pcvStsNoErr;
}

PCVAPI(PcvStatus, pcvCopyDeviceToDevice_8u_C1, (
	void*					device_src,
	void*					device_dst,
	const PcvSize			host_size
	))
{
	cl_int					errcode = CL_SUCCESS;

	size_t					src_origin[3] = {0, 0, 0};
	size_t					dst_origin[3] = {0, 0, 0};
	size_t					region[3] = {host_size.width, host_size.height, 1};
	cl_event				event_ret;
	errcode = clEnqueueCopyImage(g_device_command_queue, (cl_mem)device_src, (cl_mem)device_dst, src_origin, dst_origin, region, 0, NULL, &event_ret);
	if (errcode != CL_SUCCESS)
	{
		return pcvStsErr;
	}

	errcode = clWaitForEvents(1, &event_ret);
	if (errcode != CL_SUCCESS)
	{
		return pcvStsErr;
	}

	cl_event_info			event_info = CL_EVENT_COMMAND_EXECUTION_STATUS;
	cl_int					event_status;
	errcode = clGetEventInfo(event_ret, event_info, sizeof(cl_int), &event_status, NULL);
	if (errcode != CL_SUCCESS || event_status != CL_COMPLETE)
	{
		return pcvStsErr;
	}

	return pcvStsNoErr;
}

PCVAPI(PcvStatus, pcvCopyDeviceToDevice_8u_C1R, (
	void*					device_src,
	void*					device_dst,
	const PcvSize			host_size,
	const PcvRect			host_roi
	))
{
	(host_size);

	cl_int					errcode = CL_SUCCESS;

	size_t					src_origin[3] = {host_roi.x, host_roi.y, 0};
	size_t					dst_origin[3] = {host_roi.x, host_roi.y, 0};
	size_t					region[3] = {host_roi.width, host_roi.height, 1};
	cl_event				event_ret;
	errcode = clEnqueueCopyImage(g_device_command_queue, (cl_mem)device_src, (cl_mem)device_dst, src_origin, dst_origin, region, 0, NULL, &event_ret);
	if (errcode != CL_SUCCESS)
	{
		return pcvStsErr;
	}

	errcode = clWaitForEvents(1, &event_ret);
	if (errcode != CL_SUCCESS)
	{
		return pcvStsErr;
	}

	cl_event_info			event_info = CL_EVENT_COMMAND_EXECUTION_STATUS;
	cl_int					event_status;
	errcode = clGetEventInfo(event_ret, event_info, sizeof(cl_int), &event_status, NULL);
	if (errcode != CL_SUCCESS || event_status != CL_COMPLETE)
	{
		return pcvStsErr;
	}

	return pcvStsNoErr;
}
